
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Collections;
using System.Diagnostics;
using System.Threading;
using System.Xml;

using Swaf;
using Swaf.BizObj;
using Guid = Swaf.BGuid.Guid;
using System.Runtime.Serialization;

namespace Swaf.Call
{
    /// <summary>
    /// Summary description for RoundRobinCall.
    /// </summary>
    public class RoundRobinCall : BaseCompositeCall
    {
        protected object syncObject = new object();
        protected int m_nextCallIndex = 0;

        /// <summary>
        /// Added method to control each usage of RoundRobinCall will start from a different position, but then
        /// error handling etcetera will be performed from that base.
        /// </summary>
        /// <returns></returns>
        public int GetStartCallIndex()
        {
            int startIndex = 0;
            
            lock (syncObject)
            {
                startIndex = m_nextCallIndex;

                // Calculate the next value
                m_nextCallIndex = (m_nextCallIndex + 1) % m_internalCalls.Count;
            }

            return startIndex;
        }

        public RoundRobinCall(string id, XmlElement initInfo)
            : base(id, initInfo)
        {
        }

        public override object call(params object[] p)
        {
            Exception callException = null;
            loadInternalListIfEmpty();
            if (m_internalCalls.Count != 0)
            {
                internalCallMember c = null;
                bool called = false;
                bool callable = true;
                int startingPos = GetStartCallIndex();
                int currentPos = startingPos;
                while (callable && !called)
                {
                    try
                    {
                        c = (internalCallMember)m_internalCalls[currentPos];
                        if (c.enabled)
                        {
                            object result = c.makeCall(ref called, ref callException, p);
                            if (callException != null)
                            {
                                if (!(callException is CallException))
                                {
                                    throw new RoundRobinCallException(string.Format("RoundRobin call failed for '{0}'.", m_methodName), callException);
                                }

                                // If we received a CallException will continue the round robin attempts
                            }
                            else
                            {
                                return result;
                            }
                        }
                        else
                        {
                            //Provide a more reliable way of detecting the end of possible calling
                            for (int i = 0; i < m_internalCalls.Count; ++i)
                            {
                                if (((internalCallMember)m_internalCalls[i]).enabled)
                                {
                                    // There is still a callable member so keep calling
                                    continue;
                                }
                            }

                            // no calls available to call
                            callable = false;
                        }
                    }
                    catch (RoundRobinCallException)
                    {
                        throw;
                    }
                    catch (Exception e)
                    {
                        throw new CallException(string.Format("Unabled to make RoundRobin call for '{0}'.", m_methodName), e);
                    }
                    finally
                    {
                        //Put increment in a finally clause so on "normal" execution the increment will happend

                        //Move increment of m_nextCallIndex after all checks have been made.
                        //				 If startingPos was 0, the m_nextCallIndex would never be 0 and therefore
                        //				 this would loop indefinitely

                        //Ensure the count never goes above the number of internal callers using modulus
                        currentPos = (currentPos + 1) % m_internalCalls.Count;
                    }
                }
            }

            throw new RoundRobinCallException(string.Format("No enabled registered child calls to round robin call.  Check configuration.  Last call exception was {0}", (callException != null ? callException.ToString() : string.Empty)), callException);
        }


    }

    /// <summary>
    /// RoundRobinCallException thrown when a exception occurs calling a round robin call
    /// </summary>
    [Serializable]
    public class RoundRobinCallException : BaseException
    {
        /// <summary>
        /// Default constructor
        /// </summary>
        public RoundRobinCallException()
        {

        }

        /// <summary>
        /// Standard message constructor
        /// </summary>
        /// <param name="message"></param>
        public RoundRobinCallException(string message)
            : base(message)
        {
        }

        /// <summary>
        /// Standard message constructor with an inner exception
        /// </summary>
        /// <param name="message"></param>
        /// <param name="innerException"></param>
        public RoundRobinCallException(string message, Exception innerException)
            : base(message, innerException)
        {
        }

        /// <summary>
        /// Deserialization constructor
        /// </summary>
        /// <param name="info"></param>
        /// <param name="context"></param>
        public RoundRobinCallException(System.Runtime.Serialization.SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }
    }
}
